文章目录
  1. 1. JavaScript模块简介
  2. 2. JavaScript模块写法
    1. 2.1. 立即执行函数
    2. 2.2. 导入全局变量
    3. 2.3. 导出模块
    4. 2.4. 方法的重写
    5. 2.5. 克隆并重写方法
    6. 2.6. 模块的继承
  3. 3. 模块应用-单例模式
  4. 4. 模块的规范

前段时间,我急需这种模块化的方法来管理我的JavaScript代码
Javascript模块化编程这一系列文章给我很大帮助,介绍得很通俗易懂,小伙伴们可以看看,我将在此文基础上记录一些我的笔记。


JavaScript模块简介

JavaScript不是一种模块化编程语言,它不支持“类”(class),更不用说“模块”(module)了。(正在制定中的ECMAScript标准第六版,将正式支持”类”和”模块”,但还需要很长时间才能投入实用。)

我们可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。通过使用函数产生模块,我们几乎可以完全摒弃全局变量的使用,从而缓解这个JavaScript的最为糟糕的特性之一所带来的影响。

JavaScript模块写法

立即执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
var bar = (function (){
var count=0;
var setCount = function(start){
count=start;
}
var getCount = function(){
return count;
}
return {
setCount:setCount,
getCount:getCount,
}
})();

本例是JavaScript模块的基本写法。使用到了闭包

此时外部代码无法读取内部的count变量。

1
console.log(foo.count); //undefined

导入全局变量

把全局变量作为参数传递给一个立即执行函数,这样就完成了全局变量的导入,立即执行函数中可以使用此全局变量的方法,并可以修改(简化)全局变量的名称

1
2
3
(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));

导出模块

通过在立即执行函数中返回一个Object,将模块导出到全局空间供其他模块使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var foo = (function () {
var mod = {},
privateVariable = 1;
function privateMethod() {
console.log("this is a private method");
}
mod.moduleProperty = 2;
mod.moduleMethod = function () {
console.log(privateVariable);
};
mod.callPrivateMethod = function(){
privateMethod();
}
return mod;
}());
//test
foo.moduleMethod(); //output:1
console.log(foo.moduleProperty); //output:2
foo.callPrivateMethod(); //output:"this is a private method"
foo.privateMethod(); //undefined
console.log(foo.privateVariable); //undefined

方法的重写

在“导出模块”例子基础上,重写他的moduleMethod方法..

1
2
3
4
5
6
7
8
9
10
11
var foo = (function (mod) {
var old_moduleMethod = mod.moduleMethod;
mod.moduleMethod = function () {
// method override, has access to old through old_moduleMethod...
console.log("this is already overridden");
};
return mod;
}(foo));
//test
foo.moduleMethod(); //output:"this is already overridden"

并且你可以在新方法中访问老方法,如果需要的话。

克隆并重写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var cfoo = (function(old){
var mod = {},
key;
for (key in old) {
if (old.hasOwnProperty(key)) {
mod[key] = old[key];
}
}
var super_moduleMethod = old.moduleMethod;
mod.moduleMethod = function () {
// override method on the clone, access to super through super_moduleMethod
console.log("this is a override method on the clone");
};
return mod;
})(foo);
//test
foo.moduleMethod(); //output:1
cfoo.moduleMethod(); //output:"this is a override method on the clone"
cfoo.callPrivateMethod(); //output:"this is a private method"

此可以克隆某个模块并可根据自己需要重写其方法。

模块的继承

如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块。在”立即执行函数”例子的基础上,继承bar。

1
2
3
4
5
6
7
8
9
10
11
12
var bar = (function(mod){
mod.newFunc=function(){
console.log("this is a new function");
}
return mod;
})(bar);
//test
console.log(bar.getCount()); //output:0
bar.setCount(4);
console.log(bar.getCount()); //output:4
bar.newFunc(); //output:"this is a new function"

我们看到可以访问foo中的老函数和新方法。那他可不可以访问foo中的私有变量呢?

let’s have a try…

1
2
3
4
5
6
7
8
9
10
11
12
13
var bar = (function(mod){
mod.newFunc=function(){
console.log("this is a new function");
}
mod.testFunc=function(){
console.log(count);
console.log(this.count);
}
return mod;
})(bar);
//test
bar.testFunc(); //两种访问count方式,结果都是undefined,即说明这种方法访问不到父模块的私有变量

注意:这种方法是访问不到父模块的私有变量的。

在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象。可以用一下方式解决:

1
2
3
4
5
6
var bar = (function(mod){
mod.newFunc=function(){
console.log("this is a new function");
}
return mod;
})(bar||{});

模块应用-单例模式

模块模式通常结合单例模式(Singleton Pattern)使用。JavaScript的单例就是用对象字面量表示法创建的对象,对象的属性值可以试数值或函数,并且属性值在该对象的生命周期中不会发生变化。

单例的最佳实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Universe;
(function () {
var instance;
Universe = function Universe() {
if (instance) {
return instance;
}
instance = this;
// all the functionality
this.start_time = 0;
this.bang = "Big";
};
}());

测试一下一以上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// testing
var uni = new Universe();
var uni2 = new Universe();
console.log(uni===uni2); //output:true
// adding to the prototype
Universe.prototype.nothing = true;
var uni = new Universe();
// again adding to the prototype
// after the initial object is created
Universe.prototype.everything = true;
var uni2 = new Universe();
// linked to the objects
console.log(uni.nothing); // output:true
console.log(uni2.nothing); // output:true
console.log(uni.everything); // output:true
console.log(uni2.everything); // output:true
console.log(uni.constructor.name); // output:"Universe"
console.log(uni.constructor === Universe); // output:"Universe"

我们发现new出来的两个Universe对象是同一个所以他们是同一个对象,实现了单例,且加进原型的属性在两个对象均可访问。

关于constructor:
constructor属性不影响任何JavaScript的内部属性。instanceof检测对象的原型链,通常你是无法修改的(不过某些引擎通过私有的proto属性暴露出来)。

constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。

模块的规范

目前,通行的Javascript模块规范有:CommonJS(node.js应用此规范),AMD(异步模块定义),CMD(通用模块定义)。
主要有两个Javascript库实现了AMD规范:require.js和curl.js。
实现了CMD规范:sea.js。

关于这些库和规范的讨论相关文章有:
seaJS与RequireJS的异同
AMD 和 CMD 的区别有哪些?

先写到这,对于这些库,尚在了解中。等应用一阵再写相关的博文吧。


参考文献:
[1] Javascript模块化编程
[2] JavaScript Module Pattern: In-Depth
[3] JavaScript - The Good Parts
[4] 深入理解JavaScript系列(25):设计模式之单例模式
[5] javascript-patterns singleton

文章目录
  1. 1. JavaScript模块简介
  2. 2. JavaScript模块写法
    1. 2.1. 立即执行函数
    2. 2.2. 导入全局变量
    3. 2.3. 导出模块
    4. 2.4. 方法的重写
    5. 2.5. 克隆并重写方法
    6. 2.6. 模块的继承
  3. 3. 模块应用-单例模式
  4. 4. 模块的规范